""" Defines a Vector class.

    WARNING: This module is now deprecated in favor of `sbak.cgeometry`.
    If you use anything from in here, be warned that you will have to
    update your code later on.

    This is the Cython version of the ScrollBack vector module. When
    available, this module can be imported as `sbak.vector`. The
    Cython version can also be accessed directly as `sbak.cvector` if
    it is available.
    
    If the Cython version of the vector module is not available then
    `sbak.vector` will refer to a pure Python module with the same
    functionality as the Cython module. The Python version can also be
    accessed directly as `sbak.pvector` regardless of whether or not the
    Cython is available.
"""

cdef extern from "math.h":
    double atan2(double x, double y)
    double sin(double n)
    double cos(double n)
    double hypot(double x, double y)

DEF pi = 3.141592653589793115997963468544185161590576171875

cdef double degrees(double r):
    cdef double res
    res = r * 180.0 / pi
    return res

cdef double radians(double d):
    cdef double res
    res = d * pi / 180.0
    return res

__all__ = ['Vector']

cdef class Vector:
    """ A 2d vector class that supports both x,y axes and polar APIs.
    
        Vector()
        Vector(X, Y)
        Vector((X, Y))
        Vector(x = X, y = Y)
        Vector(deg = DEGREES, mag = MAGNITUDE)
        Vector(rad = RADIANS, mag = MAGNITUDE)
        Vector(magdeg = (MAGNITUDE, DEGREES))
        Vector(magrad = (MAGNITUDE, RADIANS))
    
        The vector class can be used to dynamically switch between two-
        dimensional, (x,y) coodinates and polar (magnitude, angle)
        coordinates. This is done by accessing various properties
        whose values are dynamically generated.
        
        For example, any vector where the x and y coordinate are
        equal (other than x == y == 0) will have an angle of -45
        degrees, or pi/4 radians. This can be seen in the following:
        
        >>> from math import pi
        >>> from sbak.vector import Vector
        >>> v = Vector(10, 10)
        >>> v.deg
        -45.0
        >>> v.rad
        -0.78539816339744828
        >>> -pi/4
        -0.78539816339744828
        
        Assignments can be done to these dynamic properties as well.
        The internal x,y values are updated to reflect the change.
        
        ~~~ A Note about Angle Direction ~~~
        
        In traditional mathematical applications, the Y axis increases
        while moving upwards and decreases while moving downwards.
        However, in SDL and many other game development platforms the
        Y axis is inverted to accomodate the fact that monitors are
        usually refreshed from top to bottom. This means that a lower
        Y value corresponds to a higher position on screen, and a
        higher value means a lower position. For this reason, the
        Vector class calculates angles (both in radian and in degrees)
        with an inverted Y axis so that increasing angles still moves
        the vector counterclockwise when shown inside Y-inverted
        systems.
        
        ~~~ A Note about Zero-Magnitude Vectors ~~~
        
        Note that properties which give angle measurements have one
        caveat that may not be obvious: They retain their value
        whenever the x and y components of the vector are set to 0
        either directly or indirectly. In other words, whenever the
        x and y values of a vector both become 0, the radian and
        degree measures of the angle will each remain unchanged.
        
        This behavior deserves some explanation: In reality, any
        vector with x and y components of eactly 0 will also have a
        magnitude of exactly 0, and this is true of the simulated
        vectors of the Vector class. However, any true vector with
        magnitue 0 points in every direction simultaneously. This
        can't be represented with a single angle measurement value,
        so attempting to compute an angle gives theoretically
        unpredicable results. (In practice the value generated by
        the computer is usually either 0 degrees or 180 degrees, but
        these values are incorrect since no angle value is actually
        possible.)
        
        To solve this problem, the Vector class takes an extra step
        whenever the x and y values are changed to preserve the
        radian measure of the angle if the x and y are both
        changed to 0. This is not mathematically correct behavior,
        but it is much more useful than having an unpredictable
        value for the angle.
    """
    
    cdef double _x, _y, _rad
    
    def __cinit__( self ):
        self._rad = 0.0
        self._x = 0.0
        self._y = 0.0
    
    def __init__( self,
                  x = None,
                  y = None,
                  xy = None,
                  mag = None,
                  deg = None,
                  rad = None,
                  magdeg = None,
                  magrad = None ):
        
        if x is not None:
            try:
                self._x, self._y = x[:2]
            except TypeError:
                self._x = x
        if y is not None:
            self._y = y
        
        if self._x != 0.0 or self._y != 0.0:
            self._rad = atan2(-self._y, self._x)
        
        if xy is not None:
            self.xy = xy
        if mag is not None:
            self.mag = mag
        if deg is not None:
            self.deg = deg
        if rad is not None:
            self.rad = rad
        if magdeg is not None:
            self.magdeg = magdeg
        if magrad is not None:
            self.magrad = magrad
    
    def __repr__(self):
        return 'Vector(%f, %f)' % self.xy
    
    property x:
        def __get__(self):
            return self._x
        
        def __set__(self, x):
            self._x = x
            if self._x != 0.0 or self._y != 0.0:
                self._rad = atan2(-self._y, self._x)
    
    property y:
        def __get__(self):
            return self._y
        
        def __set__(self, y):
            self._y = y
            if self._x != 0.0 or self._y != 0.0:
                self._rad = atan2(-self._y, self._x)
    
    property xy:
        def __get__(self):
            return self._x, self._y
            
        def __set__(self, val):
            self._x, self._y = val
            if self._x != 0.0 or self._y != 0.0:
                self._rad = atan2(-self._y, self._x)
    
    property rad:
        def __get__(self):
            return self._rad
    
        def __set__(self, double rad):
            cdef double mag
            mag = hypot(self._x, self._y)
            self._rad = -rad
            self._x = cos(self._rad) * mag
            self._y = -sin(self._rad) * mag
    
    property deg:
        def __get__(self):
            cdef double deg
            deg = degrees(self._rad)
            return deg
            
        def __set__(self, double deg):
            cdef double mag
            self._rad = radians(deg)
            mag = hypot(self._x, self._y)
            self._x = cos(self._rad) * mag
            self._y = -sin(self._rad) * mag
    
    property mag:
        def __get__(self):
            cdef double mag
            mag = hypot(self._x, self._y)
            return mag
    
        def __set__(self, double mag):
            cdef double rad
            rad = atan2(self._y, self._x)
            self._x = cos(self._rad) * mag
            self._y = -sin(self._rad) * mag
    
    property magrad:
        def __get__(self):
            cdef double mag
            mag = hypot(self._x, self._y)
            return mag, self._rad
    
        def __set__(self, magrad):
            cdef double mag
            mag, self._rad = magrad
            self._x = cos(self._rad) * mag
            self._y = -sin(self._rad) * mag
    
    property magdeg:
        def __get__(self):
            cdef double mag, deg
            mag = hypot(self._x, self._y)
            deg = degrees(self._rad)
            return mag, deg
    
        def __set__(self, magdeg):
            cdef double mag, deg
            mag, deg = magdeg
            self._rad = radians(deg)
            self._x = cos(self._rad) * mag
            self._y = -sin(self._rad) * mag
    
    def __iter__(self):
        return iter((self._x, self._y))
    
    def __len__(self):
        return 2
    
    def __getitem__(self, index):
        return (self._x, self._y).__getitem__(index)
    
    def __setitem__(self, index, value):
        xy = [self._x, self._y]
        xy.__setitem__(index, value)
        self._x, self._y = xy
        if self._x != 0.0 or self._y != 0.0:
            self._rad = atan2(-self._y, self._x)
    
    def __add__(self, other):
        v = Vector(self._x+other[0], self._y+other[1])
        if v == (0.0, 0.0):
            v.rad = self._rad
        return v
    
    def __iadd__(self, other):
        self._x += other[0]
        self._y += other[1]
        if self._x != 0.0 or self._y != 0.0:
            self._rad = atan2(-self._y, self._x)
        return self
    
    def __sub__(self, other):
        v = Vector(self._x-other[0], self._y-other[1])
        if v == (0.0, 0.0):
            v._rad = self._rad
        return v
    
    def __isub__(self, other):
        self._x -= other[0]
        self._y -= other[1]
        if self._x != 0.0 or self._y != 0.0:
            self._rad = atan2(-self._y, self._x)
        return self
    
    def __mul__(self, val):
        v = Vector(self._x*val, self._y*val)
        if v == (0.0, 0.0):
            v._rad = self._rad
        return v
    
    def __imul__(self, double val):
        self._x *= val
        self._y *= val
        if self._x != 0.0 or self._y != 0.0:
            self._rad = atan2(-self._y, self._x)
        return self
    
    def __div__(self, double val):
        v = Vector(self._x*val, self._y*val)
        if v == (0.0, 0.0):
            v._rad = self._rad
        return v
    
    def __idiv__(self, double val):
        self._x /= val
        self._y /= val
        if self._x != 0.0 and self._y != 0.0:
            self._rad = atan2(-self._y, self._x)
        return self

    def __richcmp__(self, other, int t):
        if t == 0:
            return self.xy < other
            
        elif t == 1:
            return self.xy <= other
        
        elif t == 2:
            return self.xy == other
        
        elif t == 3:
            return self.xy != other
        
        elif t == 4:
            return self.xy > other
        
        elif t == 5:
            return self.xy >= other

    def __nonzero__(self):
        return bool(self.mag)
